/*
 * Copyright (c) 2023 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "mir_pragma.h"
#include "mir_nodes.h"
#include "mir_function.h"
#include "printing.h"
#include "maple_string.h"

namespace {
enum Status {
    kStop = 0,
    kStartWithSubvec = 1,
    kNormalTypeStrEndWithSemicolon = 2,
    kNormalTypeStrEndWithSubvecNeedsSemicolon = 3,
    kEndWithSubvec = 4,
    kIgnoreAndContinue = 5
};
}

namespace maple {
static std::string GetKind(PragmaValueType kind)
{
    switch (kind) {
        case kValueByte:
            return "i8";
        case kValueShort:
            return "i16";
        case kValueChar:
            return "u16";
        case kValueInt:
            return "i32";
        case kValueLong:
            return "i64";
        case kValueFloat:
            return "f32";
        case kValueDouble:
            return "f64";
        case kValueMethodType:
            return "retype";
        case kValueMethodHandle:
            return "ref";
        case kValueString:
            return "ptr";
        case kValueType:
            return "type";
        case kValueField:
            return "var";
        case kValueMethod:
            return "func";
        case kValueEnum:
            return "enum";
        case kValueArray:
            return "array";
        case kValueAnnotation:
            return "annotation";
        case kValueNull:
            return "const";
        case kValueBoolean:
            return "u1";
        default:
            return "unknown";
    }
}

// status: 0 : unexpected char, stop
//         1 : < start subvec
//         2 : normal type str end with ';'
//         3 : normal type str end with '<' -- need append ';'
//         4 : > end subvec
//         5 : ; ignore and continue
//
//  2: Lfoofoofoofoo;xx  3: Lfoofoofoofoo<
//     |             |      |            |
//     start         end    start        end
static void GetTypeStr(const std::string &str, uint32 &start, uint32 &end, uint32 &status)
{
    uint32 i = start;
    status = kStop;
    while (str[i] == '[') {
        ++i;
    }
    start = i;
    end = i;
    switch (str[i]) {
        case 'Z':
        case 'B':
        case 'S':
        case 'C':
        case 'I':
        case 'J':
        case 'F':
        case 'D':
        case 'V':
            status = kNormalTypeStrEndWithSemicolon;
            end = i + 1;
            break;
        case 'L':
        case 'T':
            while (i < str.length()) {
                if (str[i] == ';') {
                    status = kNormalTypeStrEndWithSemicolon;
                    end = i + 1;
                    break;
                } else if (str[i] == '<') {
                    status = kNormalTypeStrEndWithSubvecNeedsSemicolon;
                    end = i;
                    break;
                } else {
                    ++i;
                }
            }
            break;
        case '<':
            status = kStartWithSubvec;
            end = i + 1;
            break;
        case '>':
            status = kEndWithSubvec;
            end = i + 1;
            break;
        case ';':
            // continue cases
            status = kIgnoreAndContinue;
            end = i + 1;
            break;
        default:
            // termination - reached unexpected char
            end = i;
            break;
    }
}

MIRPragmaElement *MIRPragma::GetPragmaElemFromSignature(const std::string &signature)
{
    if (signature.empty()) {
        return nullptr;
    }
    std::stack<MIRPragmaElement *> elemStack;
    MIRPragmaElement *elem = mod->GetMemPool()->New<MIRPragmaElement>(*mod);
    elem->SetType(kValueArray);
    elemStack.push(elem);
    uint32 status = 0;
    uint32 start = 0;
    uint32 end = 0;
    while (1) {
        if (signature.size() <= start) {
            break;
        }
        GetTypeStr(signature, start, end, status);
        // status: 0:stop/1:start_subvec/2:normal/3:normal+';'/4:end_subvec/5:ignore_and_continue
        switch (status) {
            case kStop:
                return elem;
            case kStartWithSubvec: {
                MIRPragmaElement *etmp = mod->GetMemPool()->New<MIRPragmaElement>(*mod);
                etmp->SetType(kValueArray);
                elemStack.top()->SubElemVecPushBack(etmp);
                elemStack.push(etmp);
                break;
            }
            case kNormalTypeStrEndWithSemicolon: {
                MIRPragmaElement *etmp = mod->GetMemPool()->New<MIRPragmaElement>(*mod);
                etmp->SetType(kValueType);
                std::string typeStr = signature.substr(start, end - start);
                etmp->SetU64Val(static_cast<uint64>(GlobalTables::GetStrTable().GetOrCreateStrIdxFromName(typeStr)));
                elemStack.top()->SubElemVecPushBack(etmp);
                break;
            }
            case kNormalTypeStrEndWithSubvecNeedsSemicolon: {
                MIRPragmaElement *etmp = mod->GetMemPool()->New<MIRPragmaElement>(*mod);
                etmp->SetType(kValueType);
                std::string typeStr = signature.substr(start, end - start) + ";";
                etmp->SetU64Val(static_cast<uint64>(GlobalTables::GetStrTable().GetOrCreateStrIdxFromName(typeStr)));
                elemStack.top()->SubElemVecPushBack(etmp);
                break;
            }
            case kEndWithSubvec:
                if (elemStack.empty()) {  // Invalid annotation signature format
                    return nullptr;
                }
                elemStack.pop();
                break;
            case kIgnoreAndContinue:
                break;
            default:
                DEBUG_ASSERT(false, "unexpected status");
                break;
        }
        start = end;
    }
    return elem;
}

void MIRPragmaElement::Dump(int indent) const
{
    constexpr int indentOffset = 2;
    constexpr int floatPrec = 7;
    constexpr int doublePrec = 16;
    GStrIdx gStrIdx;
    std::string str = GetKind(valueType);
    switch (valueType) {
        case kValueByte:
            LogInfo::MapleLogger() << str << " " << val.i;
            break;
        case kValueShort:
            LogInfo::MapleLogger() << str << " " << val.i;
            break;
        case kValueChar:
            LogInfo::MapleLogger() << str << " " << val.u;
            break;
        case kValueInt:
            LogInfo::MapleLogger() << str << " " << val.i;
            break;
        case kValueLong:
            LogInfo::MapleLogger() << str << " " << val.j;
            break;
        case kValueFloat:
            LogInfo::MapleLogger() << std::setiosflags(std::ios::scientific) << str << " "
                                   << std::setprecision(floatPrec) << val.f << "f";
            break;
        case kValueDouble:
            LogInfo::MapleLogger() << std::setiosflags(std::ios::scientific) << str << " "
                                   << std::setprecision(doublePrec) << val.d;
            break;
        case kValueMethodType:
            LogInfo::MapleLogger() << str << " $" << std::hex << "0x" << val.u << std::dec;
            break;
        case kValueMethodHandle:
            LogInfo::MapleLogger() << str << " " << std::hex << "0x" << val.u << std::dec;
            break;
        case kValueString:
            gStrIdx.reset(val.u);
            LogInfo::MapleLogger() << str << " \"" << GlobalTables::GetStrTable().GetStringFromStrIdx(gStrIdx) << "\"";
            break;
        case kValueType:
            gStrIdx.reset(val.u);
            LogInfo::MapleLogger() << str << " <$" << GlobalTables::GetStrTable().GetStringFromStrIdx(gStrIdx) << ">";
            break;
        case kValueField:
            gStrIdx.reset(val.u);
            LogInfo::MapleLogger() << str << " @" << GlobalTables::GetStrTable().GetStringFromStrIdx(gStrIdx);
            break;
        case kValueMethod:
            gStrIdx.reset(val.u);
            LogInfo::MapleLogger() << str << " &" << GlobalTables::GetStrTable().GetStringFromStrIdx(gStrIdx);
            break;
        case kValueEnum:
            gStrIdx.reset(val.u);
            LogInfo::MapleLogger() << str << " " << GlobalTables::GetStrTable().GetStringFromStrIdx(gStrIdx);
            break;
        case kValueArray: {
            size_t num = subElemVec.size();
            LogInfo::MapleLogger() << "array [" << num;
            if (num != 0) {
                if (num > 1) {
                    LogInfo::MapleLogger() << ",\n";
                } else {
                    LogInfo::MapleLogger() << ", ";
                }
                size_t i = 0;
                while (i < num) {
                    if (num > 1) {
                        PrintIndentation(indent + indentOffset);
                    }
                    subElemVec[i]->Dump(indent + indentOffset);
                    if (i != num - 1) {
                        LogInfo::MapleLogger() << ",\n";
                    }
                    ++i;
                }
            }
            LogInfo::MapleLogger() << "]";
            break;
        }
        case kValueAnnotation: {
            size_t num = subElemVec.size();
            LogInfo::MapleLogger() << "annotation <$";
            LogInfo::MapleLogger() << GlobalTables::GetStrTable().GetStringFromStrIdx(typeStrIdx) << "> [" << num;
            if (num) {
                if (num > 1) {
                    LogInfo::MapleLogger() << ",\n";
                } else {
                    LogInfo::MapleLogger() << ", ";
                }
                size_t i = 0;
                while (i < num) {
                    if (num > 1) {
                        PrintIndentation(indent + indentOffset);
                    }
                    LogInfo::MapleLogger()
                        << "@" << GlobalTables::GetStrTable().GetStringFromStrIdx(subElemVec[i]->nameStrIdx) << " ";
                    subElemVec[i]->Dump(indent + indentOffset);
                    if (i != num - 1) {
                        LogInfo::MapleLogger() << ",\n";
                    }
                    ++i;
                }
            }
            LogInfo::MapleLogger() << "]";
            break;
        }
        case kValueNull:
            LogInfo::MapleLogger() << str << " nullptr";
            break;
        case kValueBoolean:
            LogInfo::MapleLogger() << str << " " << val.u;
            break;
    }
}

void MIRPragma::Dump(int indent) const
{
    LogInfo::MapleLogger() << '\n';
    PrintIndentation(indent);
    LogInfo::MapleLogger() << "pragma " << static_cast<int>(visibility) << " ";
    switch (pragmaKind) {
        case kPragmaClass:
            LogInfo::MapleLogger() << "class $";
            break;
        case kPragmaFunc:
            LogInfo::MapleLogger() << "func &";
            break;
        case kPragmaField:
            LogInfo::MapleLogger() << "var @";
            break;
        case kPragmaVar:
            LogInfo::MapleLogger() << "var %";
            break;
        case kPragmaParam:
            LogInfo::MapleLogger() << "param " << paramNum << " &";
            break;
        case kPragmaFuncExecptioni:
            LogInfo::MapleLogger() << "func_ex &";
            break;
        case kPragmaFuncVar:
            LogInfo::MapleLogger() << "func_var &";
            break;
        default:
            DEBUG_ASSERT(false, "unexpected func kind");
            break;
    }
    LogInfo::MapleLogger() << GlobalTables::GetStrTable().GetStringFromStrIdx(strIdx) << " ";
    GStrIdx gStrIdx = GlobalTables::GetTypeTable().GetTypeFromTyIdx(tyIdx)->GetNameStrIdx();
    LogInfo::MapleLogger() << "<$" << GlobalTables::GetStrTable().GetStringFromStrIdx(gStrIdx) << "> ";
    if (tyIdxEx != 0u) {
        MIRType *typeEx = GlobalTables::GetTypeTable().GetTypeFromTyIdx(tyIdxEx);
        LogInfo::MapleLogger() << " " << typeEx->GetMplTypeName() << " ";
    }
    LogInfo::MapleLogger() << "{";
    for (size_t j = 0; j < elementVec.size(); ++j) {
        LogInfo::MapleLogger() << '\n';
        PrintIndentation(indent + 1);
        MIRPragmaElement *e = elementVec[j];
        gStrIdx = e->GetNameStrIdx();
        LogInfo::MapleLogger() << "@" << GlobalTables::GetStrTable().GetStringFromStrIdx(gStrIdx) << " ";
        e->Dump(indent);
        if (j != elementVec.size() - 1) {
            LogInfo::MapleLogger() << ",";
        }
    }
    LogInfo::MapleLogger() << "}";
}
}  // namespace maple
